﻿#pragma once

// Inner Fire 游戏引擎库
// DX核心类
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 这个文件引用一些通用的项目。以及最常见的功能。
// 工具辅助类应该在这个的前面。也就是说，只要是DX项目，都必然引用这个核心类。
// 而工具辅助类属于这个核心类的一部分（事实上应该把两个合了）
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-19

#include <DirectXMath.h>
#include <d3d12.h>
#include <dx_lib/d3dx12.h>
#include <memory>
#include <string>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
using namespace DirectX;

namespace ifire {

// 得到一个单位矩阵
extern const DirectX::XMFLOAT4X4 IDENTITY_4X4;
// 帧资源的数量
constexpr int FRAME_RESOURCE_COUNT = 3;
// 交换链的数量，通常是2个。
constexpr UINT SWAP_CHAIN_BUFFER_COUNT = 2;
// 默认的缓冲区的格式
constexpr auto BACK_BUFFER_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;

enum class RenderLayer : int {
  Opaque = 0,
  AlphaTested,
  AlphaTestedTreeSprites,
  Transparent,
  Mirrors,
  Reflected,
  Shadow,
  GpuWaves,
  Highlight,
  Sky,
  OpaqueDynamicReflectors,
  Debug,
  SkinnedOpaque,
  Count
};

enum class CubeMapFace : int {
  PositiveX = 0,
  NegativeX = 1,
  PositiveY = 2,
  NegativeY = 3,
  PositiveZ = 4,
  NegativeZ = 5
};

// 顶点数据
struct Vertex {
  Vertex() = default;
  Vertex(float x, float y, float z, // Pos
      float nx, float ny, float nz, // Normal
      float u, float v)             // TexCoord
      : Pos(x, y, z), Normal(nx, ny, nz), TexC(u, v) {}
  XMFLOAT3 Pos;
  XMFLOAT3 Normal;
  XMFLOAT2 TexC;
};

struct VertexV2 {
  DirectX::XMFLOAT3 Pos;
  DirectX::XMFLOAT3 Normal;
  DirectX::XMFLOAT2 TexC;
  DirectX::XMFLOAT3 TangentU;
};

struct SkinnedVertex
{
    DirectX::XMFLOAT3 Pos;
    DirectX::XMFLOAT3 Normal;
    DirectX::XMFLOAT2 TexC;
    DirectX::XMFLOAT3 TangentU;
    DirectX::XMFLOAT3 BoneWeights;
    BYTE BoneIndices[4];
};

// 用于树的公告牌
struct TreeSpriteVertex {
  XMFLOAT3 Pos;
  XMFLOAT2 Size;
};

// 用于纹理的累加（计算着色器）
struct Data {
  XMFLOAT3 v1;
  XMFLOAT2 v2;
};
const int NUM_DATA_ELEMETS = 32;

constexpr auto MAX_LIGHTS = 16;

/*
 * 这个排列的顺序不能更改，是有意义的。因为HLSL的结构体封装规则使用填充对齐。
 * 要将结构体中的元素打包为一个Vector4向量。而如果用一个Vector3+float就可以成功打包。
 */
struct Light {
  // 光源的颜色
  DirectX::XMFLOAT3 Strength = {0.5f, 0.5f, 0.5f};
  // 用于Point和Spot
  float FalloffStart = 1.0f;
  // 光源的方向，用于directional和Spot
  DirectX::XMFLOAT3 Direction = {0.0f, -1.0f, 0.0f};
  // 光源的有效（最大）距离。用于Point和Spot
  float FalloffEnd = 10.0f;
  // 用于Point和Spot
  DirectX::XMFLOAT3 Position = {0.0f, 0.0f, 0.0f};
  // 仅用于SpotLight
  float SpotPower = 64.0f;
};

// 常量缓冲区
struct ObjectConstants {
  DirectX::XMFLOAT4X4 world = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 tex_transform = IDENTITY_4X4;
  DirectX::XMFLOAT2 DisplacementMapTexelSize = {1.0f, 1.0f};
  float GridSpatialStep = 1.0f;
  float Pad;
};

struct ObjectConstantsV2 {
  DirectX::XMFLOAT4X4 World = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 TexTransform = IDENTITY_4X4;
  UINT MaterialIndex;
  UINT ObjPad0;
  UINT ObjPad1;
  UINT ObjPad2;
};

// 用于蒙皮骨骼
struct SkinnedConstants
{
    DirectX::XMFLOAT4X4 BoneTransforms[96];
};

// 用于实例化
struct InstanceData {
  DirectX::XMFLOAT4X4 World = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 TexTransform = IDENTITY_4X4;
  UINT MaterialIndex;
  UINT InstancePad0;
  UINT InstancePad1;
  UINT InstancePad2;
};

constexpr UINT OBJECT_CB_SIZE =
    (static_cast<UINT>(sizeof(ObjectConstants)) + 255) & ~255;

struct PassConstants {
  DirectX::XMFLOAT4X4 View = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 InvView = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 Proj = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 InvProj = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 ViewProj = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 InvViewProj = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 ViewProjTex = IDENTITY_4X4;
  DirectX::XMFLOAT4X4 ShadowTransform = IDENTITY_4X4;
  DirectX::XMFLOAT3 EyePosW = {0.0f, 0.0f, 0.0f};
  float per_object_pad1 = 0.0f;
  DirectX::XMFLOAT2 RenderTargetSize = {0.0f, 0.0f};
  DirectX::XMFLOAT2 InvRenderTargetSize = {0.0f, 0.0f};
  float NearZ = 0.0f;
  float FarZ = 0.0f;
  float TotalTime = 0.0f;
  float DeltaTime = 0.0f;
  DirectX::XMFLOAT4 AmbientLight = {0.0f, 0.0f, 0.0f, 1.0f};
  DirectX::XMFLOAT4 FogColor = {0.7f, 0.7f, 1.0f, 1.0f};
  float gFogStart = 5.0f;
  float gFogRange = 150.0f;
  DirectX::XMFLOAT2 cbPerObjectPad2;
  Light Lights[MAX_LIGHTS];
};
constexpr UINT MAIN_PASS_CB_SIZE =
    (static_cast<UINT>(sizeof(PassConstants)) + 255) & ~255;

struct SsaoConstants {
  DirectX::XMFLOAT4X4 Proj;
  DirectX::XMFLOAT4X4 InvProj;
  DirectX::XMFLOAT4X4 ProjTex;
  DirectX::XMFLOAT4 OffsetVectors[14];

  // For SsaoBlur.hlsl
  DirectX::XMFLOAT4 BlurWeights[3];

  DirectX::XMFLOAT2 InvRenderTargetSize = {0.0f, 0.0f};

  // Coordinates given in view space.
  float OcclusionRadius = 0.5f;
  float OcclusionFadeStart = 0.2f;
  float OcclusionFadeEnd = 2.0f;
  float SurfaceEpsilon = 0.05f;
};

struct MaterialConstants {
  DirectX::XMFLOAT4 diffuse_albedo = {1.0f, 1.0f, 1.0f, 1.0f};
  DirectX::XMFLOAT3 fresnel_r0 = {0.01f, 0.01f, 0.01f};
  float roughness = 0.25f;
  DirectX::XMFLOAT4X4 mat_transform = IDENTITY_4X4;
};

constexpr UINT MATERIAL_CB_SIZE =
    (static_cast<UINT>(sizeof(MaterialConstants)) + 255) & ~255;

class DxException {
public:
  DxException() = default;
  DxException(HRESULT hr, const std::string& functionName,
      const std::string& filename, int lineNumber);

  std::string ToString() const;

  HRESULT ErrorCode = S_OK;
  std::string FunctionName;
  std::string Filename;
  int LineNumber = -1;
};

#ifndef HASSERT
#define HASSERT(x)                                                             \
  {                                                                            \
    HRESULT hr__ = (x);                                                        \
    std::string wfn = std::string(__FILE__);                                   \
    if (FAILED(hr__)) {                                                        \
      throw DxException(hr__, #x, wfn, __LINE__);                              \
    }                                                                          \
  }
#endif

#ifndef ThrowIfFailed
#define ThrowIfFailed(x)                                                       \
  {                                                                            \
    HRESULT hr__ = (x);                                                        \
    std::string wfn = std::string(__FILE__);                                   \
    if (FAILED(hr__)) {                                                        \
      throw DxException(hr__, #x, wfn, __LINE__);                              \
    }                                                                          \
  }
#endif

/*
 * 球面坐标系
 */
struct Spherical {
  float theta;
  float phi;
  float radius;

  Spherical() = default;
  Spherical(float _theta, float _phi, float _radius)
      : theta(_theta), phi(_phi), radius(_radius) {}

  // 获得当前球面坐标系的观察矩阵
  DirectX::XMMATRIX GetViewMatrix() const;
};

//////////////////////////////////////////////////////////////////////////////
// 函数项目
//////////////////////////////////////////////////////////////////////////////
std::wstring AnsiToWString(const std::string& str);

// 计算三角形的法线向
XMVECTOR compute_normal(FXMVECTOR p0, FXMVECTOR p1, FXMVECTOR p2);
// 得到逆矩阵的转置。这个东西用来计算法线的非等比缩放
XMMATRIX inverse_transpose(CXMMATRIX m);

} // namespace ifire